package com.david.core.event;

import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 仿eventbus的事件发布/订阅框架，对比eventBus添加了直接对订阅者方法订阅的功能（执行时间比遍历方法更快些）。
 *
 * example:
 * 参考：
 * {@link com.david.core.event.TestEvent}
 * {@link com.david.basemodule.MainActivity}
 *
 *
 * 注册：
 *
 * MyEventBus.getDefault().register(this, "testEvent", TestEvent::class.java)
 * {@link com.david.basemodule.MainActivity#onCreate(Bundle)}
 *
 * 解注册：
 * MyEventBus.getDefault().unregister(this, "testEvent", TestEvent::class.java)
 * {@link com.david.basemodule.MainActivity#onDestroy()}
 *
 * 发送：
 * MyEventBus.getDefault().post(new TestEvent());
 * {@link #post(Object)}
 *
 * 接收方法：
 * {@link com.david.basemodule.MainActivity#testEvent(TestEvent)}
 *
 *
 * 注：相比于EventBus没有粘性event及自动搜索父类订阅方法等一些功能，但MyEventBus理论上应该能满足平时80-90%的需求
 *
 *
 * 由于使用了方法名，因此在混淆时订阅方法不要混淆，如在混淆规则中添加：
 * -keep public class com.david.andserver.MainActivity {
 *     public void onServerStatus(...);
 *     //或public void on**(...);
 * }
 * */
public class MyEventBus {
    private volatile static MyEventBus defaultInstance;

    /*
     * In newer class files, compilers may add methods. Those are called bridge or synthetic methods.
     * EventBus must ignore both. There modifiers are not public but defined in the Java class file format:
     * http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.6-200-A.1
     */
    private static final int BRIDGE = 0x40;
    private static final int SYNTHETIC = 0x1000;

    private static final int MODIFIERS_IGNORE = Modifier.ABSTRACT | Modifier.STATIC | BRIDGE | SYNTHETIC;

    private ExecutorService fixedExecutorService;


    /**
     * loop()运行在主线程的handler，用于切换到主线程
     * */
    private final Handler mainThreadHandler = new Handler(Looper.getMainLooper());

    /**
     * event与method订阅方法对应的map，post过来event之后遍历执行对应列表里的方法
     * */
    private volatile Map<Object, ArrayList<SubscriberMethod>> eventMethodMap = new HashMap<>();

    public static MyEventBus getDefault() {
        MyEventBus instance = defaultInstance;
        if (instance == null) {
            synchronized (MyEventBus.class) {
                instance = MyEventBus.defaultInstance;
                if (instance == null) {
                    instance = MyEventBus.defaultInstance = new MyEventBus();
                }
            }
        }
        return instance;
    }

    /**
     * 根据方法名注册，相对于遍历方法节省时间（对于类中订阅方法少的类较适用）
     * */
    public void register(Object subscriber, String methodName, Class eventClass) {
        try {
            Method method = subscriber.getClass().getDeclaredMethod(methodName, eventClass);

            int modifiers = method.getModifiers();
            if ((modifiers & Modifier.PUBLIC) == 0 || (modifiers & MODIFIERS_IGNORE) != 0) {
                throw new EventBusException(subscriber.getClass().getName() +"."+methodName +
                        " is a illegal @Subscribe method: must be public, non-static, and non-abstract");
            }

            Class<?>[] parameterTypes = method.getParameterTypes();
            if (parameterTypes.length != 1) {
                throw new EventBusException("@Subscribe method " + methodName +
                        "must have exactly 1 parameter but has " + parameterTypes.length);
            } else if (!parameterTypes[0].getName().equals(eventClass.getName())) {
                /*订阅方法中的event参数与注册方法中传入的event参数不对应*/
                throw new EventBusException("method parameter " + parameterTypes[0].getName() +
                        " is not equals the input eventType:" + eventClass.getName());
            }

            addMethodToMap(subscriber, eventClass, method);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

    private void addMethodToMap(Object subscriber, Object eventClass, Method method) {
        Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
        if (subscribeAnnotation == null) {
            Log.w("eventbus", subscriber.getClass().getName() + "." +method.getName() + "'s didn't has annotation of Subscribe.");
            return;
        }
        if (eventMethodMap.get(eventClass) == null) {
            ArrayList<SubscriberMethod> methods = new ArrayList<>();
            methods.add(new SubscriberMethod(method, subscribeAnnotation.threadMode(), subscriber));
            eventMethodMap.put(eventClass, methods);
        } else {
            SubscriberMethod subscriberMethod = new SubscriberMethod(method, subscribeAnnotation.threadMode(), subscriber);
            if (!eventMethodMap.get(eventClass).contains(subscriberMethod)) {
                eventMethodMap.get(eventClass).add(subscriberMethod);
            }
        }
    }

    /***
     * @param subscriber 订阅者
     */
    public void register(Object subscriber){
        findMethodAndRegister(subscriber);
    }

    /**
     * 查找类中订阅方法并添加到map中，TODO 查找并添加父类中的订阅方法
     * */
    private void findMethodAndRegister(Object subscriber) {
        Method[] methods;
        try {
            // This is faster than getMethods, especially when subscribers are fat classes like Activities
            methods = subscriber.getClass().getDeclaredMethods();
        } catch (Throwable th) {
            // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
            th.printStackTrace();
            return;
        }
        for (Method method : methods) {
            int modifiers = method.getModifiers();
            if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
                Class<?>[] parameterTypes = method.getParameterTypes();
                if (parameterTypes.length == 1) {
                    Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                    if (subscribeAnnotation != null) {
                        addMethodToMap(subscriber, parameterTypes[0], method);
                    }
                } else if (method.isAnnotationPresent(Subscribe.class)) {
                    String methodName = method.getDeclaringClass().getName() + "." + method.getName();
                    throw new EventBusException("@Subscribe method " + methodName +
                            "must have exactly 1 parameter but has " + parameterTypes.length);
                }
            } else if (method.isAnnotationPresent(Subscribe.class)) {
                String methodName = method.getDeclaringClass().getName() + "." + method.getName();
                throw new EventBusException(methodName +
                        " is a illegal @Subscribe method: must be public, non-static, and non-abstract");
            }
        }
    }

    /**
     * post方法：获取对应的订阅方法列表并遍历在相应线程执行
     * */
    public void post(Object eventObject) {
        ArrayList<SubscriberMethod> subscriberMethods = eventMethodMap.get(eventObject.getClass());
        if (subscriberMethods == null) {
            Log.w("eventbus", "the map of eventMethodMap is empty.");
            return;
        }
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            try {
                if (subscriberMethod.threadMode == ThreadMode.ASYNC) {
                    invokeInSubThread(eventObject, subscriberMethod);
                } else if (subscriberMethod.threadMode == ThreadMode.BACKGROUND) {
                    if (Looper.getMainLooper() != Looper.myLooper()) {
                        subscriberMethod.method.invoke(subscriberMethod.subscriber, eventObject);
                    } else {
                        invokeInSubThread(eventObject, subscriberMethod);
                    }
                } else if ((subscriberMethod.threadMode == ThreadMode.MAIN || subscriberMethod.threadMode == ThreadMode.MAIN_ORDERED)) {
                    if (Looper.getMainLooper() != Looper.myLooper()) {
                        //mode为MAIN则post到主线程执行
                        invokeInMainThread(eventObject, subscriberMethod);
                    } else {
                        subscriberMethod.method.invoke(subscriberMethod.subscriber, eventObject);
                    }
                } else {//POSTING
                    //mode为其他则直接在当前线程执行
                    subscriberMethod.method.invoke(subscriberMethod.subscriber, eventObject);
                }
            } catch (IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }

    private void invokeInSubThread(Object eventObject, SubscriberMethod subscriberMethod) {
        if (fixedExecutorService == null) {
            fixedExecutorService = Executors.newFixedThreadPool(3);
        }
        fixedExecutorService.submit(() -> {
            try {
                subscriberMethod.method.invoke(subscriberMethod.subscriber, eventObject);
            } catch (IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
            }
        });
    }

    private void invokeInMainThread(Object eventObject, SubscriberMethod subscriberMethod) {
        mainThreadHandler.post(() -> {
            try {
                subscriberMethod.method.invoke(subscriberMethod.subscriber, eventObject);
            } catch (IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
            }
        });
    }

    /***
     * @param subscriber 订阅者
     */
    public void unregister(Object subscriber){
        Set<Object> keys = eventMethodMap.keySet();
        HashMap<Object, ArrayList<SubscriberMethod>> tempMap = new HashMap<>();
        for (Object key:keys) {
            ArrayList<SubscriberMethod> subscriberMethods = eventMethodMap.get(key);
            ArrayList<SubscriberMethod> tempMethods = new ArrayList<>();

            for (SubscriberMethod item:subscriberMethods) {
                if (item.subscriber == subscriber) {
                    tempMethods.add(item);
                }
            }

            subscriberMethods.removeAll(tempMethods);
            if (subscriberMethods.size() > 0) {
                tempMap.put(key, subscriberMethods);
            }
        }
        eventMethodMap = tempMap;
    }

    public void unregister(Object subscriber, String methodName, Object eventClass) {
        ArrayList<SubscriberMethod> subscriberMethods = eventMethodMap.get(eventClass);
        if (subscriberMethods != null) {

            ArrayList<SubscriberMethod> removeList = new ArrayList<>();
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                if (methodName.equals(subscriberMethod.getMethod().getName()) && subscriberMethod.subscriber == subscriber) {
                    removeList.add(subscriberMethod);
                }
            }
            subscriberMethods.removeAll(removeList);
            if (subscriberMethods.size() == 0) {
                eventMethodMap.remove(eventClass);
            }
        }
    }
}
